// #include "http_client.h"
#include <string>
#include "util/output.h"
#include "util/defer.h"


using ErrorMsg = boost::error_info<struct tag_err_msg, std::string>;

template <>
HttpClientImpl<true>::HttpClientImpl(const std::string &host, uint16_t port)
    : _version{11}
    , _host(host)
    , _port(port)
    , _io_context{}
    , _resolver(_io_context)
    , _context(
        _io_context,
        ssl::context::tlsv12_client)
    , _keep_alive(true)
    , _connected(false)
{
    connect();
}

template <>
HttpClientImpl<false>::HttpClientImpl(const std::string &host, uint16_t port)
    : _version{11}
    , _host(host)
    , _port(port)
    , _io_context{}
    , _resolver(_io_context)
    , _context(_io_context)
    , _keep_alive(true)
    , _connected(false)
{
    connect();
}

// template <bool ssl>
// HttpClientImpl<ssl>::HttpClientImpl(net::io_context &ctx, const std::string &host, uint16_t port)
//     : _version{11}
//     , _host(host)
//     , _port(port)
//     , _ctx{ctx}
//     , _resolver(_ctx)
//     , _stream(_ctx)
//     , _keep_alive(true)
//     , _connected(false)
//     , _ssl_ctx(ssl::context::tlsv12_client)
//     , _ssl_stream(_ctx, _ssl_ctx)
// {
// }

// template <bool ssl>
// HttpClientImpl<ssl>::HttpClientImpl(tcp::socket &&socket)
//     : _version{11}
//     , _host(socket.local_endpoint().address().to_string())
//     , _port(socket.local_endpoint().port())
//     , _ctx{_inner_ctx}
//     , _resolver(_ctx)
//     , _stream(std::move(socket))
//     , _keep_alive(true)
//     , _connected(false)
//     , _ssl_ctx(ssl::context::tlsv12_client)
//     , _ssl_stream(_ctx, _ssl_ctx)
// {
// }

// template <bool ssl>
// HttpClientImpl<ssl>::HttpClientImpl(net::io_context &ctx, tcp::socket &&socket)
//     : _version{11}
//     , _host(socket.local_endpoint().address().to_string())
//     , _port(socket.local_endpoint().port())
//     , _ctx{ctx}
//     , _resolver(_ctx)
//     , _stream(std::move(socket))
//     , _keep_alive(true)
//     , _connected(false)
//     , _ssl_ctx(ssl::context::tlsv12_client)
//     , _ssl_stream(_ctx, _ssl_ctx)
// {
// }

// template <bool ssl>
// void HttpClientImpl<ssl>::register_handler(HandlerMapPtr ptr)
// {
//     _handler_map_ptr = ptr;
// }

template <bool ssl>
void HttpClientImpl<ssl>::reconnect()
{
    disconnect();
    connect();
}

template <bool ssl>
void HttpClientImpl<ssl>::connect()
{
    if (_connected) return;
    if constexpr(ssl) {
        auto const results = _resolver.resolve(_host, std::to_string(_port));
        _context.context.set_verify_mode(ssl::verify_none);
        if(! SSL_set_tlsext_host_name(_context.stream.native_handle(), _host.c_str())) {
            beast::error_code ec{static_cast<int>(::ERR_get_error()), net::error::get_ssl_category()};
            BOOST_THROW_EXCEPTION(HttpException() << ErrorMsg(ec.message()));
        }
        beast::get_lowest_layer(_context.stream).connect(results);
        _context.stream.handshake(ssl::stream_base::client);
        _connected = true;
    }
    else {
        auto const results = _resolver.resolve(_host, std::to_string(_port));
        _context.stream.connect(results);
    }
}

template <bool ssl>
void HttpClientImpl<ssl>::disconnect()
{
    if (!_connected) return ;
    _connected = false;
    return;

    /*
    boost::beast::error_code ec;
    if (_enable_ssl) {
        COUT << "enable ssl";
        _ssl_stream.shutdown(ec);
    }
    else {
        _stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
    }
    _connected = false;
    if (ec == net::error::eof) {
        CERR << ec.message();
        ec = {};
    }
    if (ec) {
        CERR << ec.message();
        throw beast::system_error{ec};
        // BOOST_THROW_EXCEPTION(ec);
    }
    */
}

template <bool ssl>
HttpClientImpl<ssl>::~HttpClientImpl()
{
    COUT << "destroy and disconnect host:" << _host;
}

template <bool ssl>
void HttpClientImpl<ssl>::setParam(const Http::UrlParam &url_param)
{
    _url_param = url_param;
}

template <bool ssl>
void HttpClientImpl<ssl>::setParam(const Http::HeadParam &head_param)
{
    _head_param = head_param;
}

template <bool ssl>
void HttpClientImpl<ssl>::setParam(const Http::StringBody &body)
{
    _body = body;
}

template <bool ssl>
template <typename Body>
void HttpClientImpl<ssl>::request(http::response<Body> &resp)
{
    _buffer.clear();
    int retry = 0;
    if (_req.keep_alive()) {
        retry = 1;
    }
    do {
        try {
            if (!_keep_alive) {
                connect();
            }
            http::write(_context.stream, _req);
            http::read(_context.stream, _buffer, resp);
            break;
        }
        catch (std::exception &e) {
            if (--retry >= 0) {
                reconnect();
            }
            else {
                BOOST_THROW_EXCEPTION(e);
                // throw;
            }
        }
    } while(retry >= 0);
}

template <bool ssl>
template <typename Body>
void HttpClientImpl<ssl>::async_request(Http::ResponseHandler<Body> handler)
{
    _buffer.clear();
    int retry = 0;
    if (_req.keep_alive()) {
        retry = 1;
    }
    do {
        try {
            do_async_request(handler);
            break;
        }
        catch (std::exception &e) {
            CERR << e.what();
            if (--retry >= 0) {
                reconnect();
            }
            else {
                BOOST_THROW_EXCEPTION(e);
            }
        }
    } while(retry >= 0);
}

template <bool ssl>
template <typename Body>
void HttpClientImpl<ssl>::do_async_request(Http::ResponseHandler<Body> handler)
{
    if (!_keep_alive) {
        connect();
    }
    auto self(this->shared_from_this());
    beast::http::async_write(_context.stream, _req, [handler, self, this](beast::error_code const &ec, size_t )
        {   
            if (ec) {
                handler(ec, http::response<Body>{});
                return;
            }
            auto resp_ptr = std::make_shared<http::response<Body>>();

            beast::http::async_read(_context.stream, _buffer, *resp_ptr, [handler, self, resp_ptr, this](beast::error_code const &ec, size_t )
                {   
                    handler(ec, *resp_ptr);
                }   
            );  
        }); 
}
